package com.marianmedla.jane16.lib;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import com.marianmedla.jane16.lib.Phrase;

/**
 * Compute syntactical similarity of the text. <br>
 * Some texts are too similar to each other , like almost! duplicated news articles.<br>
 * The difference could be that in the middle of the text is different advertisement<br>
 * or just headline is slightly modified.<p>
 * This class tries to compute how much (in percentage) are two texts similar.<p>
 * Note: <br>
 * <i>This is syntactical similarity , not lexical one. It means that only structure of words and phrases is 
 * taken into account not their meaning. <br> </i> 
 * @author marianmedla
 *
 */
public class SyntacticalSimilarity {

	private String textA;
	private String textB;

	// it turns out that this proportion is closest to the reality
	private int filter = 4;// the smaller number the far from reality, the
							// bigger one the slower performance
	private int items = 7;

	public static boolean DEBUG = false;

	private static MessageDigest md5 = null;
	static {
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}

	public SyntacticalSimilarity(String textA, String textB) {
		this.textA = textA;
		this.textB = textB;
	}

	/**
	 * Compute similarity of two texts.
	 * @return how much are the articles similar, in percentage (bigger number equals more similar 100% equals the same-roughly)
	 * @throws Exception
	 */ 
	public float compute() throws Exception {
		
		if( textA == null || textB == null){
			throw new Exception( "one of the provided text is null" );
		}

		//prepare first text to chunks
		ArrayList<Phrase> preparedText = new ArrayList<Phrase>();
		U.prepareText(filter, textA, md5, preparedText, items);

		//prepare second text to chunks
		ArrayList<Phrase> preparedText2 = new ArrayList<Phrase>();
		U.prepareText(filter, textB, md5, preparedText2, items);

		int counter = 0;
		//used phrases to avoid duplicates
		HashSet<String> used = new HashSet<String>();
		//distance of a block in textA and textB 
		HashMap<Integer, Integer> distance = new HashMap<Integer, Integer>();
		int prevDist = 0;
		for (Phrase phrase2 : preparedText2) {

			for (Phrase phrase : preparedText) {
				if (phrase.text.equals(phrase2.text)
						& !used.contains(phrase.text)) {
					int dist = Math.abs(phrase.position - phrase2.position);
					if (DEBUG) {
						System.out.println(phrase.position + " : "
								+ phrase2.position + "=" + dist + "("
								+ prevDist + ") ---" + phrase.text);
					}
					used.add(phrase.text);

					if (prevDist != dist) {
						counter = 0;
					}
					if (dist > 0) {
						distance.put(dist, ++counter);
					}
					prevDist = dist;
				}
			}
		}
		
		//-- finalization and results ---
		if(DEBUG){
		   System.out.println("Summary: text1 size=" +preparedText.size() + ", text2 size="+preparedText2.size() + ", match="+distance.size() );
	       System.out.println("Distance Factors: " + distance );
		}
	    
		if(distance.size() == 0){
		       return textA.length() == textB.length() ? 100.0f /*very same articles*/: 0.0f; //no similarity at all 
		}
		
		int shift        =  shift(distance);
	    float smaller    =  preparedText.size() < preparedText2.size() ? preparedText.size() : preparedText2.size();
	    float onepc      = (smaller/100f);
	    float pc         = (smaller-shift)/onepc; 
	    float similarity = (100-pc);
	    
	    if(DEBUG){
		    System.out.println("Shift: " + shift + " results = " + similarity + "%");
	    }

	    return similarity;
	    
	}
	
	/**
	 * Add up the individual distances from text to one single distance , called shift.
	 *  
	 * @param distance
	 * @return  
	 * @throws Exception
	 */
	private int shift( HashMap<Integer,Integer> distance ) throws Exception{
		int shift = 0;
		for ( Integer i : distance.keySet()) {
			 shift += distance.get(i);
		}
		return shift;
	}

}
